#include <iostream>
using namespace std;
// 多态的两个条件：
// 1、虚函数的重写 2、父类对象的指针或引用去调用虚函数

// 满足多态：跟指向对象有关，执行哪个对象调用就是它的虚函数
// 不满足多态：跟调用对象的类型有关，类型是什么调用的就是谁的虚函数
class Person
{
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person
{
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	/*注意：在重写基类虚函数时，派生类的虚函数在不加virtual关键字时，虽然也可以构成重写(因为继承后
 基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范，不建议这样使用*/
	/*void BuyTicket() { cout << "买票-半价" << endl; }*/
};

void Func(Person &p)
{
	p.BuyTicket();
}
void test1()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
}

// 虚函数重写的两个例外:1、协变(基类与派生类虚函数返回值类型不同)

// 派生类重写基类虚函数时，与基类虚函数返回值类型不同。
// 即基类虚函数返回基类对象的指针或者引用，派生类虚函数返回派生类对象的指针或者引用时，称为协变。
class A
{
};
class B : public A
{
};
// 下面返回类型A和B是父子关系
class Person2
{
public:
	virtual A *f() { return new A; }
};
class Student2 : public Person2
{
public:
	virtual B *f() { return new B; }
};

void Func(Person2 &p)
{
	p.f();
}

void test2()
{
	Person2 ps;
	Student2 st;
	Func(ps);
	Func(st);
}

// 虚函数重写的两个例外:2、析构函数的重写(基类与派生类析构函数的名字不同)
// 如果基类的析构函数为虚函数，此时派生类析构函数只要定义，无论是否加virtual关键字，
// 都与基类的析构函数构成重写，虽然基类与派生类析构函数名字不同。虽然函数名不相同，看起来违背了重写的规则，
// 其实不然，这里可以理解为编译器对析构函数的名称做了特殊处理，编译后析构函数的名称统一处理成destructor。

// 为什么析构函数需要重写呢？
class Person3
{
public:
	~Person3() { cout << "~Person3()" << endl; }
};

class Student3 : public Person3
{
public:
	~Student3() { cout << "~Student3()" << endl; }
};
// 这是没有重写的析构函数
void test3()
{
	// 下面的delete对象调用析构函数不构成多态，p1和p2指向的对象调用的析构函数都是Person3
	// 没有构成多态，那就不会调用Student3的析构，资源也不会被清理
	Person3 *p1 = new Person3;
	Person3 *p2 = new Student3;
	delete p1;
	delete p2;
}

class Person4
{
public:
	virtual ~Person4() { cout << "~Person4()" << endl; }
};

class Student4 : public Person4
{
public:
	virtual ~Student4() { cout << "~Student4()" << endl; }
};
// 这是重写的析构函数
void test4()
{
	// 只有派生类Student4的析构函数重写了Person4的析构函数，下面的delete对象调用析构函数，才能构成
	// 多态，才能保证p1和p2指向的对象正确的调用析构函数
	Person4 *p1 = new Person4;
	Person4 *p2 = new Student4;
	delete p1;
	delete p2;
}

class A2
{
public:
	virtual void func(int val = 1) { std::cout << "A2->" << val << std::endl; }
	virtual void test() { func(); }
};
class B2 : public A2
{
public:
	void func(int val = 0) { std::cout << "B2->" << val << std::endl; }
};

void test5()
{
	B2 *p = new B2;
	// 输出是：B2->1
	// 虽然 B2 类中重写的 func 函数有默认参数 val = 0，但默认参数是在编译时确定的，而不是运行时。
	// 在 A2 类的 test 函数中调用 func() 时，使用的是 A2 类中 func 函数的默认参数 val = 1。
	// 因此，最终调用 B2 类的 func 函数时，传入的参数值是 1，输出结果为 B2->1。
	p->test();
}

// 抽象类
// 抽象类不能实例化出对象。派生类继承后也不能实例化出对象，只有重写纯虚函数，派生类才能实例化出对象。
// 纯虚函数规范了派生类必须重写，另外纯虚函数更体现出了接口继承
class Car
{
public:
	// 这种虚函数后面等于0的叫做纯虚函数，包含纯虚函数的类叫做抽象类（也叫接口类）
	virtual void Drive() = 0;
};
class Baoma : public Car{};
class Benz : public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW : public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};

void test6()
{
	//抽象类不能实例化对象
	//Car car;
	//抽象类不能实例化对象抽象类的派生类必须重写纯虚函数，否则无法实例化
	//Baoma baoma;
	Car *pBenz = new Benz;
	pBenz->Drive();
	Car *pBMW = new BMW;
	pBMW->Drive();
	//纯虚函数的作用：强制子类完成重写
	//表示抽象的类型。 抽象就是现实中没有对应实体的
}

int main()
{
	// test1();
	// test2();
	// test3();
	// test4();
	//test5();
	test6();
	return 0;
}